package com.eqxiu.ieditor.utils;


import com.eqxiu.ieditor.interceptor.Iterators;
import com.eqxiu.ieditor.interceptor.Tuple;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import io.swagger.models.Swagger;
import org.springframework.http.ResponseEntity;
import springfox.documentation.schema.Model;
import springfox.documentation.schema.ModelProperty;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.service.*;
import springfox.documentation.swagger2.mappers.ServiceModelToSwagger2MapperImpl;

import java.util.*;

/**
 * Http接口返回{@link ResponseEntity}的, 取其范型类型作为文档的Example Value,
 * 并支持用{@link JsonPropertyOrder} 规定文档的字段顺序
 *
 * @Author LinSH
 * @Date: 下午3:23 2018-03-29
 */
public class CustomModelToSwagger2Mapper extends ServiceModelToSwagger2MapperImpl {
    @Override
    public Swagger mapDocumentation(Documentation from) {
        Multimap<String, ApiListing> apiListings = rebuildApis(from.getApiListings());
        Documentation doc = new Documentation(
                from.getGroupName(),
                null,
                from.getTags(),
                apiListings,
                from.getResourceListing(),
                new HashSet<>((from.getProduces())),
                new HashSet<>((from.getConsumes())),
                from.getHost(),
                new HashSet<>((from.getSchemes())),
                from.getVendorExtensions());
        return super.mapDocumentation(doc);
    }

    private Map<String, Model> rebuildModels(Map<String, Model> models) {
        return Iterators.map(models, new Iterators.Function<Map.Entry<String, Model>, Tuple.Tuple2<String, Model>>() {
            @Override
            public Tuple.Tuple2<String, Model> apply(Map.Entry<String, Model> e) {
                Model model = e.getValue();
                Class<?> clazz = model.getType().getErasedType();
                final JsonPropertyOrder a = clazz.getAnnotation(JsonPropertyOrder.class);
                if (a != null) {
                    final int n = model.getProperties().size();
                    final List<String> list = Arrays.asList(a.value());
                    Map<String, ModelProperty> properties = Iterators.map(model.getProperties(),
                            new Iterators.Function<Map.Entry<String, ModelProperty>, Tuple.Tuple2<String, ModelProperty>>() {
                                @Override
                                public Tuple.Tuple2<String, ModelProperty> apply(Map.Entry<String, ModelProperty> entry) {
                                    ModelProperty property = entry.getValue();
                                    int i = list.indexOf(entry.getKey());
                                    int position = property.getPosition();
                                    final ModelReference modelRef = property.getModelRef();
                                    property = position != 0 ? property : new ModelProperty(
                                            property.getName(),
                                            property.getType(),
                                            property.getQualifiedType(),
                                            i < 0 ? n : i,
                                            property.isRequired(),
                                            property.isHidden(),
                                            property.isReadOnly(),
                                            property.getDescription(),
                                            property.getAllowableValues(),
                                            property.getExample(),
                                            property.getPattern(),
                                            property.getVendorExtensions()
                                    );
                                    if (property.getModelRef() == null) {
                                        property.updateModelRef(new Function<ResolvedType, ModelReference>() {
                                            @Override
                                            public ModelReference apply(ResolvedType input) {
                                                return modelRef;
                                            }
                                        });
                                    }
                                    return Tuple.of(entry.getKey(), property);
                                }
                            });
                    model = new Model(
                            model.getId(),
                            model.getName(),
                            model.getType(),
                            model.getQualifiedType(),
                            properties,
                            model.getDescription(),
                            model.getBaseModel(),
                            model.getDiscriminator(),
                            model.getSubTypes(),
                            model.getExample()
                    );
                }
                return Tuple.of(e.getKey(), model);
            }
        });
    }

    private Multimap<String, ApiListing> rebuildApis(Multimap<String, ApiListing> apiListings) {
        ImmutableMultimap.Builder<String, ApiListing> builder = ImmutableMultimap.builder();
        for (Map.Entry<String, ApiListing> ae : apiListings.entries()) {
            ApiListing value = ae.getValue();
            Map<String, Model> models = rebuildModels(value.getModels());
            List<ApiDescription> apis = rebuildApis(models, value.getApis());
            ApiListing listing = new ApiListing(
                    value.getApiVersion(),
                    value.getBasePath(),
                    value.getResourcePath(),
                    value.getProduces(),
                    value.getConsumes(),
                    value.getHost(),
                    value.getProtocols(),
                    value.getSecurityReferences(),
                    apis,
                    models,
                    value.getDescription(),
                    value.getPosition(),
                    value.getTags()
            );
            builder.put(ae.getKey(), listing);
        }
        return builder.build();
    }

    private List<ApiDescription> rebuildApis(final Map<String, Model> models, List<ApiDescription> apis) {
        return Iterators.map(apis, new Iterators.Function<ApiDescription, ApiDescription>() {
            @Override
            public ApiDescription apply(ApiDescription api) {
                List<Operation> operations = Iterators.map(api.getOperations(), new Iterators.Function<Operation, Operation>() {
                    @Override
                    public Operation apply(Operation operation) {
                        ModelReference responseModel = operation.getResponseModel();
                        String type = responseModel.getType();
                        Model model = models.get(type);
                        if (model != null) {
                            Class<?> clazz = model.getType().getErasedType();
                            if (ResponseEntity.class.isAssignableFrom(clazz)) {
                                final ModelProperty property = model.getProperties().get("body");
                                if (property != null) {
                                    final ModelReference modelRef = property.getModelRef();
                                    Map<String, List<AuthorizationScope>> securityReferences = operation.getSecurityReferences();
                                    List<SecurityReference> references = Iterators.mapToList(securityReferences,
                                            new Iterators.Function<Map.Entry<String, List<AuthorizationScope>>, SecurityReference>() {
                                                @Override
                                                public SecurityReference apply(Map.Entry<String, List<AuthorizationScope>> e) {
                                                    return new SecurityReference(e.getKey(), e.getValue().toArray(new AuthorizationScope[e.getValue().size()]));
                                                }
                                            });
                                    Set<ResponseMessage> responseMessages = Iterators.map(operation.getResponseMessages(),
                                            new Iterators.Function<ResponseMessage, ResponseMessage>() {
                                                @Override
                                                public ResponseMessage apply(ResponseMessage v) {
                                                    return new ResponseMessage(v.getCode(),
                                                            v.getMessage(),
                                                            v.getCode() == 200 ? modelRef : v.getResponseModel(),
                                                            v.getHeaders(),
                                                            v.getVendorExtensions()
                                                    );
                                                }
                                            });
                                    return new Operation(operation.getMethod(),
                                            operation.getSummary(),
                                            operation.getNotes(),
                                            modelRef,
                                            operation.getUniqueId(),
                                            operation.getPosition(),
                                            operation.getTags(),
                                            operation.getProduces(),
                                            operation.getConsumes(),
                                            operation.getProtocol(),
                                            references,
                                            operation.getParameters(),
                                            responseMessages,
                                            operation.getDeprecated(),
                                            operation.isHidden(),
                                            operation.getVendorExtensions());
                                }
                            }
                        }
                        return operation;
                    }
                });
                return new ApiDescription(api.getPath(), api.getDescription(), operations, api.isHidden());
            }
        });
    }
}


